package cmd

import (
    "errors"
    "fmt"
    "io"
    "os"
    "path/filepath"
    "regexp"
    "runtime"
    "strings"

    "gitee.com/exi-red/maketools/util"
    "github.com/spf13/cobra"

    "github.com/klauspost/compress/flate"
    z "github.com/klauspost/compress/zip"
)

const (
    // Zip 子命令
    Zip = "zip"

    // ZipDesc 描述
    ZipDesc = "将指定目录打包为 zip 文件"
)

// ZipFlags zip 子命令参数结构
type ZipFlags struct {
    Dir              Params // 需要打包的目录
    Zip              Params // zip 生成文件路径
    ExcludeSuffixes  Params // 要排除的后缀列表（如 .log）
    ExcludePatterns  Params // 要排除的正则表达式列表
    CompressionLevel Params // 压缩级别 (0-9, 0=不压缩, 1=最快, 9=最佳)
    Parallelism      Params // 并行压缩文件数 (0=自动, 8=使用8个CPU核心)
}

// ZipFlag zip 子命令参数
var ZipFlag = ZipFlags{
    Dir: Params{
        Name:      "dir",
        Shorthand: "d",
        Value:     "",
        Usage:     "需要打包的目录",
    },
    Zip: Params{
        Name:      "zip",
        Shorthand: "o",
        Value:     "",
        Usage:     "zip 生成文件路径",
    },
    ExcludeSuffixes: Params{
        Name:      "exclude-suffixes",
        Shorthand: "e",
        Value:     "",
        Usage:     "要排除的后缀列表（如 .log）",
    },
    ExcludePatterns: Params{
        Name:      "exclude-patterns",
        Shorthand: "p",
        Value:     "",
        Usage:     "要排除的正则表达式列表",
    },
    CompressionLevel: Params{
        Name:      "compression-level",
        Shorthand: "l",
        IntValue:  6,
        Usage:     "压缩级别 (0-9, 0=不压缩, 1=最快, 9=最佳)",
    },
    Parallelism: Params{
        Name:      "parallelism",
        Shorthand: "P",
        IntValue:  0,
        Usage:     "并行压缩文件数 (0=自动, 8=使用8个CPU核心)",
    },
}

// 注册子命令 Zip
var zipCmd = &cobra.Command{
    Use:   Zip,
    Short: ZipDesc,
    Long:  util.CLIHeader(40, ZipDesc),
    Example: strings.NewReplacer(
        "{ROOTNAME}", ROOTNAME,
        "{zip}", Zip,
        "{Dir}", ZipFlag.Dir.Name,
        "{Zip}", ZipFlag.Zip.Name,
        "{ExcludeSuffixes}", ZipFlag.ExcludeSuffixes.Name,
        "{ExcludePatterns}", ZipFlag.ExcludePatterns.Name,
        "{CompressionLevel}", ZipFlag.CompressionLevel.Name,
        "{Parallelism}", ZipFlag.Parallelism.Name,
    ).Replace(
        `  {ROOTNAME} {zip} ^ 
    --{Dir}               "./dir" ^
    --{Zip}               "dest/demo.zip|/path/to/demo.zip" ^
    --{ExcludeSuffixes}  ".log,/dir" ^
    --{ExcludePatterns}  "_test.go$,/(debug|test)/,(.*).rtf$" ^
    --{CompressionLevel} 6 ^
    --{Parallelism}       8`,
    ),
    Run: func(cmd *cobra.Command, args []string) {
        // 处理器执行
        err := zipHanlder(cmd)
        if err != nil {
            fmt.Println(err)
            os.Exit(1)
        }
    },
}

// 初始化
func init() {
    // 禁用自动排序
    zipCmd.Flags().SortFlags = false

    // 解析参数
    StringP(zipCmd, ZipFlag.Dir)
    StringP(zipCmd, ZipFlag.Zip)
    StringP(zipCmd, ZipFlag.ExcludeSuffixes)
    StringP(zipCmd, ZipFlag.ExcludePatterns)
    IntP(zipCmd, ZipFlag.CompressionLevel)
    IntP(zipCmd, ZipFlag.Parallelism)

    // 自定义帮助信息
    BoolP(zipCmd, "help", "h", false, "显示 zip 命令的帮助")
}

// Zip 打包处理器
func zipHanlder(cmd *cobra.Command) error {
    flags := cmd.Flags()

    // 要打包的目录
    dir, _ := flags.GetString(ZipFlag.Dir.Name)
    if dir == "" {
        return errors.New("source directory not specified")
    }

    // zip 输出路径
    output, _ := flags.GetString(ZipFlag.Zip.Name)
    if output == "" {
        return errors.New("zip output path not specified")
    }

    // 获取可选参数
    suffix, _ := flags.GetString(ZipFlag.ExcludeSuffixes.Name)
    pattern, _ := flags.GetString(ZipFlag.ExcludePatterns.Name)
    level, _ := flags.GetInt(ZipFlag.CompressionLevel.Name)
    parallelism, _ := flags.GetInt(ZipFlag.Parallelism.Name)

    // 默认值
    if level == 0 {
        level = 6
    }

    // 默认值
    if parallelism == 0 {
        parallelism = 2
    }

    // 格式化
    dir = strings.TrimSpace(dir)
    dir = util.WorkDirPath(dir)
    if dir != filepath.Clean(dir) {
        return errors.New("the packaging target directory cannot be outside the current directory")
    }

    // 目录不存在
    fileInfo, err := os.Stat(dir)
    if os.IsNotExist(err) {
        return errors.New("the packaging target directory does not exist")
    }

    // 不是目录
    if !fileInfo.IsDir() {
        return errors.New("the packaging target is not a directory")
    }

    // 输出路径
    output = util.PathFormat(output)

    // 排除后缀
    suffixes := make([]string, 0)
    for _, suffix := range strings.Split(suffix, ",") {
        suffix = strings.TrimSpace(suffix)
        if suffix != "" {
            suffixes = append(suffixes, suffix)
        }
    }

    // 按正则排除
    patterns := make([]string, 0)
    for _, pattern := range strings.Split(pattern, ",") {
        pattern = strings.TrimSpace(pattern)
        if pattern != "" {
            patterns = append(patterns, pattern)
        }
    }

    // 验证压缩级别
    if level < 0 || level > 9 {
        return fmt.Errorf("invalid compression level %d, must be 0-9", level)
    }

    // 创建ZIP文件
    zipFile, err := os.Create(output)
    if err != nil {
        return fmt.Errorf("failed to create ZIP file:%v", err)
    }
    defer func(zipFile *os.File) {
        _ = zipFile.Close()
    }(zipFile)

    // 初始化ZIP写入器
    zipWriter := z.NewWriter(zipFile)
    defer func(zipWriter *z.Writer) {
        _ = zipWriter.Close()
    }(zipWriter)

    // 配置压缩方法
    zipWriter.RegisterCompressor(
        z.Deflate, func(w io.Writer) (io.WriteCloser, error) {
            return flate.NewWriter(w, level)
        },
    )

    // 设置并行压缩
    runtime.GOMAXPROCS(parallelism)

    // 预编译正则表达式
    var excludeRegexps []*regexp.Regexp
    for _, pattern := range patterns {
        re, err := regexp.Compile(pattern)
        if err != nil {
            return fmt.Errorf("invalid regular expression%q: %v", pattern, err)
        }
        excludeRegexps = append(excludeRegexps, re)
    }

    // 遍历目录
    err = filepath.Walk(
        dir, func(path string, info os.FileInfo, err error) error {

            if err != nil {
                return err
            }

            // 跳过根目录本身
            if path == dir {
                return nil
            }

            // 获取相对路径
            relPath, err := filepath.Rel(dir, path)
            if err != nil {
                return err
            }

            // 检查是否应该排除
            if shouldExclude(path, info, suffixes, excludeRegexps) {
                if info.IsDir() {
                    return filepath.SkipDir // 跳过整个目录
                }
                return nil // 跳过文件
            }

            // 创建ZIP文件头
            header, err := z.FileInfoHeader(info)
            if err != nil {
                return err
            }

            // 确保路径使用正斜杠
            header.Name = filepath.ToSlash(relPath)
            header.Method = z.Deflate // 使用标准 ZIP 压缩算法

            // 如果是目录，只需要创建目录头
            if info.IsDir() {
                header.Name += "/"
                _, err = zipWriter.CreateHeader(header)
                return err
            }

            // 添加文件到ZIP
            writer, err := zipWriter.CreateHeader(header)
            if err != nil {
                return err
            }

            // 打开源文件
            file, err := os.Open(path)
            if err != nil {
                return err
            }
            defer func(file *os.File) {
                _ = file.Close()
            }(file)

            // 复制文件内容
            _, err = io.Copy(writer, file)
            return err
        },
    )

    return nil
}

// shouldExclude 检查文件/目录是否应该被排除
func shouldExclude(path string, info os.FileInfo, suffixes []string, excludeRegexps []*regexp.Regexp) bool {

    // 确保路径使用正斜杠
    path = filepath.ToSlash(path)

    // 检查目录排除
    if info.IsDir() {
        // 检查后缀排除
        lowerPath := strings.ToLower(path)
        for _, suffix := range suffixes {
            if strings.HasSuffix(lowerPath, strings.ToLower(suffix)) {
                return true
            }
        }
        // 检查正则表达式排除
        for _, re := range excludeRegexps {
            if re.MatchString(path) {
                return true
            }
        }
        return false
    }

    // 检查后缀排除
    lowerPath := strings.ToLower(path)
    for _, suffix := range suffixes {
        if strings.HasSuffix(lowerPath, strings.ToLower(suffix)) {
            return true
        }
    }

    // 检查正则表达式排除
    for _, re := range excludeRegexps {
        if re.MatchString(path) {
            return true
        }
    }

    return false
}
